import 'dart:async';
import 'dart:convert';

import 'package:stomp_dart_client/stomp_dart_client.dart';
import 'package:my_flutter_core/my_flutter_core.dart';

typedef UnsubscribeFn = void Function(
    {Map<String, String>? unsubscribeHeaders});

/// It handles stomp subscriptions and their status.
class StompSubscriber {
  StompSubscriber(this._stompClient) {
    _id =
        '${DateHelper.getCurrentDateTime().millisecondsSinceEpoch}-${SharedUtil.generateRandomIntBetweenRange(100000, 999999)}';
  }

  final StompClient _stompClient;
  final String _clientOnDestinationPrefix = '/topic';
  final Map<UnsubscribeFn, bool> _unsubscriptions = {};

  late String _id;

  String get id {
    return _id;
  }

  /// Subscribes destination over a web socket connection and resturn its id.
  /// You can use this id to unsubscribe manually or use clear method to unsubcribe all existing subscriptions.
  /// param Contains server destination path and header info.
  UnsubscribeFn subscribe({
    required String destination,
    void Function(dynamic res)? callback,
    Map<String, String>? headers,
  }) {
    final unsubscribeFn = _stompClient.subscribe(
      destination: destination,
      callback: (msg) {
        callback?.call(json.decode(msg.body!));
      },
      headers: headers,
    );

    _unsubscriptions[unsubscribeFn] = true;

    return unsubscribeFn;
  }

  /// Subscribes and returns data as promise. Unsubscribes automatically once the data returned.
  /// param Contains server destination path and header info.
  Future<Map<String, dynamic>> subscribeOnce({
    required String destination,
    Map<String, String>? headers,
  }) async {
    final completer = Completer<Map<String, dynamic>>();

    final unsubscribeFn = _stompClient.subscribe(
      destination: destination,
      callback: (msg) {
        completer.complete(json.decode(msg.body!));
      },
      headers: headers,
    );

    // Unsubscribes once data comes.
    completer.future.whenComplete(() => unsubscribe(unsubscribeFn, null));

    return completer.future;
  }

  /// Unsubscribes by [UnsubscribeFn] and header info.
  void unsubscribe(UnsubscribeFn unsubscribeFn, Map<String, String>? headers) {
    // Unsubscribes from remote server.
    unsubscribeFn(unsubscribeHeaders: headers);

    // Removes from local id list.
    _unsubscriptions.remove(unsubscribeFn);
  }

  /// Unsubcribes subscriptions based on ids and header info.
  void unsubscribeAll(
      List<UnsubscribeFn> unsubscribeFns, Map<String, String>? headers) {
    for (final p in unsubscribeFns) {
      unsubscribe(p, headers);
    }
  }

  /// Sends data to destination over a web socket connection.
  /// param Contains server destination path, header info and web socket body.
  void send({
    required String destination,
    Map<String, String>? headers,
    WebSocketBody? webSocketBody,
  }) {
    String? body;

    if (webSocketBody != null) {
      body = json.encode(webSocketBody.toJson());
    }

    _stompClient.send(destination: destination, body: body, headers: headers);
  }

  /// Sends data to destination over a web socket connection and gets response right away.
  /// param Contains server destination path, header info and web socket body.
  Future<Map<String, dynamic>> sendAndGet({
    required String destination,
    Map<String, String>? headers,
    WebSocketBody? webSocketBody,
  }) async {
    // Generates unique client on destination path based on server destination path.
    final clientOnDestination =
        '$_clientOnDestinationPrefix$destination-${SharedUtil.generateRandomIntBetweenRange(100000, 999999)}';

    // Creates subscription before sending.
    final future = subscribeOnce(destination: clientOnDestination);

    // Uses auto generated client on destination path for server sending back.
    webSocketBody ??= WebSocketBody();
    webSocketBody.clientOnDestination = clientOnDestination;

    send(
      destination: destination,
      headers: headers,
      webSocketBody: webSocketBody,
    );

    return future;
  }

  /// Unsubcribes all existing subscriptions.
  void clear(Map<String, String>? headers) {
    unsubscribeAll(_unsubscriptions.keys.toList(), headers);
  }
}
